#!/usr/bin/python2

import socket
import sys
import asyncore
import weakref
from network import JSONConnection, IndexConnection
import world
import config
import logging
import euclid
import time

class ClientInfo:
    def __init__(self, client_id, host, port, position):
        self.id = client_id
        self.host = host
        self.port = port
        self.set_position(position)
        
    def set_position(self, pos):
        self.pos = euclid.Vector2(*pos)
        self.inside_block = world.inside_block(block_coord, self.pos)
        
    def __ne__(self, other):
        return self.id != other.id

#clients we are aware of the position
#client id and ClientInfo
clients = {}

#clients that are connected to our server
client_connections = weakref.WeakValueDictionary()

#connections to other block servers
block_connections = weakref.WeakValueDictionary()

#blocks that will be received from the index server
blocks = None

server = None

last_update_time = 0.

def aura_intersect():
    """
    returns all clients that have auras with non null intersections
    """
    
    result = {}

    for client1 in clients.values():
        if not client1.inside_block:
            continue
        
        peer_set = set()
        for client2 in clients.values():
            if client1 != client2:
                if (client1.pos - client2.pos).magnitude_squared() < config.aura_size_sq:
                    peer_set.add(client2.id)
        result[client1.id] = peer_set
                    
    return result

def process_auras():
    """
    send to clients the other clients they should be connected to
    """
    
    intersections = aura_intersect()
        
    for client_id, conn in client_connections.items():
        try:
            conn.send_peers(list(intersections[client_id]))
        except KeyError:
            continue

class ClientHandler(JSONConnection, asyncore.dispatcher):
    def __init__(self, sock):
        JSONConnection.__init__(self)
        asyncore.dispatcher.__init__(self, sock)
        
        #binds a bunch of commands with actions
        self._commands = {"client_hello" : self.client_hello,
                          "client_info" : self.client_info
                          }
        
    def client_hello(self, message):
        client_id = message["id"]
        logging.info("Client connected with ID = %s", client_id)  
        
        remote_host, _ = self.socket.getpeername()
        self.client = ClientInfo(client_id, remote_host, message["port"], message["pos"])
        
        clients[client_id] = self.client
        client_connections[client_id] = self
        
    def client_info(self, message):
        clients[self.client.id].set_position(message["pos"])
        
    def handle_close(self):
        self.close()

        if hasattr(self, "client"):
            logging.info("Block : Client %s disconnected", self.client.id)
            
            try:
                del clients[self.client.id]
            except KeyError:
                pass
            

    def send_peers(self, peers):
        message = {}
        message["cmd"] = "aura_peers"
        
        client_list = []
        for client_id in peers:
            client = clients[client_id]
            client_list.append((client_id, (client.host, client.port)))
        
        message["peers"] = client_list
        self.write_json(message)

                
class BlockServer(asyncore.dispatcher):
    def __init__(self, host='0.0.0.0'):
        asyncore.dispatcher.__init__(self)

        logging.info("Starting server at %s, %s", host, listen_port)

        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((host, listen_port))
        self.listen(16)

    def handle_accept(self):
        pair = self.accept()
        if pair:
            sock, addr = pair
            logging.debug("Incoming connection from %s", repr(addr))
            ClientHandler(sock)
            
            
class IndexHandler(IndexConnection):
    """
    Connects to the index server
    """
    
    def __init__(self):
        IndexConnection.__init__(self)
        
        self._commands["index_response"] = self.index_response
        
        msg = {}
        msg["cmd"] = "block_hello"
        msg["coord"] = block_coord
        msg["port"] = listen_port
        self.write_json(msg)
        
    def handle_connect(self):
        logging.debug("Connected to index")

    def handle_close(self):
        self.close()
        if not self.response_received:
            logging.error("Index server connection closed without a response")
            sys.exit(-1)

    def index_response(self, msg):
        logging.info("Received index response")
        self.response_received = True
        
        global blocks
        blocks = world.unformat_blocks(msg["blocks"])
        
def port_number(base, x, y):
    """
    calculates the port number we will bind to
    fro mthe command line parameters
    """
    
    return base + x + (world.xmax + 1)*y 

if __name__ == "__main__":
    if len(sys.argv) >= 4:
        _, base, x, y = sys.argv
        base, x, y = int(base), int(x), int(y)
        
        block_coord = x, y
        listen_port = port_number(base, x, y)
        
        #filename='block-%s.log' % listen_port, 
        logging.basicConfig(format="%(levelname)s " + ("BLOCK(%s, %s) " % (x, y)) + "%(asctime)-15s %(message)s" , level=logging.DEBUG)
        logging.info("Starting")

        server = BlockServer()
        IndexHandler()

        while True:
            #since there is always socket event to process
            #poll retuns instantly
            #so we need a manual timer
            current_time = time.time()
            
            if current_time - last_update_time > 0.5:
                process_auras()
                last_update_time = current_time
            asyncore.loop(use_poll=True, count=1, timeout=0.5)
    else:
        print "Usage : server_block.py port_base x y"
    
